package de.szut.sudoku.game.field;

import java.util.ArrayList;
import java.util.Random;

import de.szut.sudoku.main.GameController;

/**
 * Class for generating the sudoku-field
 * 
 * @author Marc Huisinga
 * 
 */

public class FieldGenerator {
	private SudokuField startSudokuField;
	private SudokuField goalSudokuField;
	private SudokuField currentSudokuField;

	public SudokuField getGoalSudokuField() {
		return goalSudokuField;
	}

	public void setGoalSudokuField(SudokuField goalSudokuField) {
		this.goalSudokuField = goalSudokuField;
	}

	public SudokuField getStartSudokuField() {
		return startSudokuField;
	}

	public void setStartSudokuField(SudokuField startSudokuField) {
		this.startSudokuField = startSudokuField;
	}

	public SudokuField getCurrentSudokuField() {
		return currentSudokuField;
	}

	public void setCurrentSudokuField(SudokuField currentSudokuField) {
		this.currentSudokuField = currentSudokuField;
	}

	/**
	 * Checks if the game has ended
	 * 
	 * @return
	 */
	public boolean gameHasEnded() {
		if (goalSudokuField.deepEquals(currentSudokuField)) {
			return true;
		}
		return false;
	}

	/**
	 * Generates the sudokufield
	 */
	public void generateSudokuField() {
		System.out.println("Sudokufield is being generated ...");
		boolean fieldGenerated = false;

		// regenerates the field until the expected amount of preset boxes has
		// been reached
		while (!fieldGenerated) {
			// Generates the boxes
			startSudokuField = new SudokuField();
			ArrayList<Box> boxes = new ArrayList<Box>();
			for (FieldGroup currentGroup : startSudokuField
					.getHorizontalGroups()) {
				for (Box currentBox : currentGroup.getBoxes()) {
					boxes.add(currentBox);
				}
			}
			// Fills the field
			Random randomGenerator = new Random();
			int index = 0;
			// Backtracks the generatior of the field
			while (index < 81) {
				Box currentBox = boxes.get(index);
				// gets the fieldgroups related to the current box
				FieldGroup[] relatedFieldGroups = startSudokuField
						.getRelatedFieldgroups(currentBox);

				// checks if there are choices available for the box
				if (currentBox.getAvailableStates().size() > 0) {
					// gets a random value from the available values
					int number = currentBox.getAvailableStates().get(
							randomGenerator.nextInt(currentBox
									.getAvailableStates().size()));
					boolean containsValue = false;
					// checks if the value is already contained within the
					// current group
					for (FieldGroup currentGroup : relatedFieldGroups) {
						if (currentGroup.containsValue(number)) {
							containsValue = true;
						}
					}
					// removes the value from the available values if the value
					// is duplicate
					if (containsValue) {
						currentBox.getAvailableStates().remove(
								new Integer(number));
						// sets the state of the value and walks one box forward
					} else {
						currentBox.setState(number);
						index++;
					}
					// resets the available states of the box and walks back one
					// box should there not be any available choices anymore
				} else {
					currentBox.resetAvailableStates();
					currentBox.setState(0);
					index--;
				}
			}

			// clones the field to save the goal field
			goalSudokuField = startSudokuField.clone();

			// resets all the available states (for no reason at all!)
			for (Box currentBox : boxes) {
				currentBox.resetAvailableStates();
			}

			// gets the amount of preset boxes
			int presetBoxes = GameController.getInstance().getdContainer()
					.getCurrentDifficulty().getWrittenBoxes();

			index = 80;

			// pokes holes into the field while there are still boxes that can
			// be removed and the amount of preset boxes has not been reached
			// yet
			while (index >= presetBoxes && boxes.size() != 0) {
				// gets a random box
				Box currentBox = boxes
						.get(randomGenerator.nextInt(boxes.size()));
				// gets the field groups related to the current box
				FieldGroup[] relatedFieldGroups = startSudokuField
						.getRelatedFieldgroups(currentBox);
				// saves the old value and resets the box
				int oldValue = currentBox.getState();
				currentBox.setState(0);

				// gets the unique states in the related fieldgroups
				ArrayList<Integer> allUniqueStates = new ArrayList<Integer>();
				for (FieldGroup currentGroup : relatedFieldGroups) {
					int[] currentStates = currentGroup
							.getStatesWithoutDuplicates();
					for (int currentState : currentStates) {
						if (!allUniqueStates.contains(currentState)) {
							allUniqueStates.add(currentState);
						}
					}
				}

				// should the field still have one, unique solution, then the
				// algorithm moves to the next box
				// otherwise resets the box
				if (allUniqueStates.size() < 8) {
					currentBox.setState(oldValue);
				} else {
					index--;
				}
				boxes.remove(currentBox);
			}

			// sets that the field has been generated if it has been generated
			if (boxes.size() != 0) {
				fieldGenerated = true;
				// clones the field to the current field
				currentSudokuField = startSudokuField.clone();
			}

		}
	}

}
